""" Implements the rectangle classes for sbak.geometry.

    This class should be considered an "implementation detail" and should not
    be accessed directly.
"""

import operator
from math import pi, atan2, hypot, sin, cos, degrees, radians

from point import FrozenPoint, FrozenFPoint

__all__ = [
    'AbstractRect',
    'Rect',
    'FRect'
]

class AbstractRect(object):
    """ Base for rectangle classes.
    
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        NOTE:
            
            This class is only a base. It has two methods, called `conv` and
            `pconv` respectively, which must be overridden by a subclass in
            order for the class to work properly.
            
            `conv` should be a callable that accepts one argument and returns
            that argument converted to a certain numeric type, such as int or
            float. (The `int` and `float` builtins work fine for this purpose.)
            
            `pconv` should be a callable that accepts one or two arguments and
            returns a value that is compatible with the frozen point classes.
            (The frozen point classes themselves can be used just fine.)
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    """
    
    __slots__ = ['_pos', '_size', '_x', '_y', '_w', '_h']
    
    def __init__(self, *argv):
        """ Initialize a rect object.
        
            Rect(r) -> Rect(r[0], r[1], r[2], r[3])
            Rect(pos, size) -> Rect(pos[0], pos[1], size[0], size[1])
            Rect(x, y, w, h) - > Rect(x, y, w, h)
        """
        
        argc = len(argv)
        
        if argc == 1:
            (self._x, self._y), (self._w, self._h) = self._pos, self._size =\
                map(self.pconv, argv[0])
        elif argc == 2:
            (self._x, self._y), (self._w, self._h) = self._pos, self._size =\
                map(self.pconv, argv)
        elif argc == 4:
            (self._x, self._y), (self._w, self._h) = self._pos, self._size =\
                self.pconv(arg[:2]), self.pconv(arg[2:])
        else:
            raise TypeError("1, 2, or 4 arguments required.")
    
    @staticmethod
    def conv(val):
        """ Dummy method; should be overridden by a subclass. """
        raise NotImplementedError("This method should have been overridden by "
                                  "a subclass!")
    
    @staticmethod
    def pconv(val):
        """ Dummy method; should be overridden by a subclass. """
        raise NotImplementedError("This method should have been overridden by "
                                  "a subclass!")
    
    x = property(operator.attrgetter("_x"), doc =
        """ Position of left edge. """)
    @x.setter
    def x(self, x):
        self._x = x = self.conv(x)
        self._pos = self.pconv((x, self.y))
    
    y = property(operator.attrgetter("_y"), doc =
        """ Position of top edge. """)
    @y.setter
    def y(self, y):
        self._y = y = self.conv(y)
        self._pos = self.pconv((self.x, y))
    
    w = property(operator.attrgetter("_w"), doc =
        """ Width; difference between left and right. """)
    @w.setter
    def w(self, w):
        self._w = w = self.conv(w)
        self._size = self.pconv((w, self.h))
        
    width = w
    
    h = property(operator.attrgetter("_h"), doc =
        """ Height; difference between top and bottom. """)
    @h.setter
    def h(self, h):
        self._h = h = self.conv(h)
        self._size = self.pconv((self.w, h))
        
    height = h
        
    left = x
    
    top = y
    
    @property
    def right(self):
        """ Position of right edge. """
        return self.x + self.w
    @right.setter
    def right(self, val):
        self.x = val - self.w
        
    @property
    def bottom(self):
        """ Position of bottom edge. """
        return self.y + self.h
    @bottom.setter
    def bottom(self, val):
        self.y = val - self.h
        
    pos = property(operator.attrgetter("_pos"), doc =
        """ x and y together as a frozen point. """)
    @pos.setter
    def pos(self, pos):
        self._pos = self._x, self._y = self.pconv(pos)
    
    size = property(operator.attrgetter("_size"), doc =
        """ w and h together as a frozen point. """)
    @size.setter
    def size(self, size):
        self._size = self._w, self._h = self.pconv(size)
        
    topleft = pos
    
    @property
    def topright(self):
        """ Position of top-right corner as a frozen point. """
        return self.pos + (self.w, 0)
    @topright.setter
    def topright(self, val):
        self.pos = self.pconv(val) - (self.w, 0)
    
    @property
    def bottomleft(self):
        """ Position of the bottom-left corner as a frozen point. """
        return self.pos + (0, self.h)
    @bottomleft.setter
    def bottomleft(self, val):
        self.pos = self.pconv(val) - (0, self.h)
    
    @property
    def bottomright(self):
        """ Position of the bottom-left corner as a frozen point. """
        return self.pos + self.size
    @bottomright.setter
    def bottomright(self, val):
        self.pos = self.pconv(val) - self.size
        
    @property
    def center(self):
        """ Position of the exact center as a frozen point. """
        return self.pos + (self.size / 2)
    @center.setter
    def center(self, val):
        self.pos = val - (self.size / 2)
        
    @property
    def midleft(self):
        """ Position of the middle of the left edge as a point. """
        return self.pos + (0, self.h/2)
    @midleft.setter
    def midleft(self, val):
        self.pos = self.pconv(val) - (0, self.h/2)
    
    @property
    def midright(self):
        """ Position of the middle of the right edge as a point. """
        return self.pos + (self.w, self.h/2)
    @midright.setter
    def midright(self, val):
        self.pos = self.pconv(self) - (self.w, self.h/2)
    
    @property
    def midtop(self):
        """ Position of the middle of the top edge as a frozen point. """
        return self.pos + (self.x/2, 0)
    @midtop.setter
    def midtop(self, val):
        self.pos = self.pconv(val) - (self.x/2, 0)
    
    @property
    def midbottom(self):
        """ Position of the middle of the bottom edge as a frozen point. """
        return self.pos + (self.x/2, self.h)
    @midbottom.setter
    def midbottom(self, val):
        self.pos = self.pconv(val) - (self.x/2, self.h)
    
    def move(self, x, y=None):
        """ Returns a new rect moved by the indicated distance.
        
            r.move(d) -> Rect(r.x+d[0], r.y+d[1], r.w, r.h)
            r.move(x, y) -> Rect(r.x+x, r.y+y, r.w, r.h)
        """
        if y is not None:
            x = x, y
        return self.__class__(self.pos + x, self.size)

    def move_ip(self, x, y=None):
        """ Moves the rect in-place by the given amount.
        
            r.move(d)
            r.move(x, y)
        """
        if y is not None:
            x = x, y
        self.pos += x
    
    def collidepoint(self, x, y=None):
        """ Returns whether or not the given point is within the rect.
        
            r.collidepoint(p) -> bool
            r.collidepoint(x, y) -> bool
            
            `p` can be any sequence of two numbers.
        """
        
        if y is None:
            x,y  = x
        return (x >= self.left and x <= self.right
            and y >= self.top and y <= self.bottom)
            
    def colliderect(self, r2):
        """ Returns whether or not the other rect collides with this one.
        
            r1.colliderect(r2) -> bool
            
            `r2` can be any sequence of four numbers of the form (x, y, w, h),
            as in a rectangle.
            
            This returns true if any part of the rectangles overlap.
        """
        left2, top2, w2, h2 = r2
        right2, bottom2 = left2+w2, top2+h2
        return (right2 >= self.left and left2 <= self.right
            and bottom2 >= self.top and top2 <= self.bottom)
    
    def __getitem__(self, index):
        return (self.x, self.y, self.w, self.h)[index]
    
    def __setitem__(self, index, val):
        if index == 0:
            self.x = val
        elif index == 1:
            self.y = val
        elif index == 2:
            self.w = val
        elif index == 3:
            self.h = val
        else:
            raise IndexError("index must be a value in the range 0 to 3.")
    
    def __iter__(self):
        for val in self.x, self.y, self.w, self.h:
            yield val
            
    def __len__(self):
        return 4
    
    def __eq__(self, val):
        try:
            return self.x, self.y, self.w, self.h == tuple(val)
        except TypeError:
            return False
    
    def __nonzero__(self):
        return self.w or self.h

class Rect(AbstractRect):
    """ A rect class with integer precision. """
    
    conv = int
    pconv = FrozenPoint

class FRect(AbstractRect):
    """ A rect class with floating-point precision. """
    
    conv = float
    pconv = FrozenFPoint